public class Network {
    private static final int OCTETS_IN_ADDRESS = 4;
    private static final int BITS_IN_BYTE = 8;
    private static final int OCTET_MAX_VALUE = 255;
    private static Network[] privateNetworks;
    private int maskLength;
    private IPv4Address address;
    private IPv4Address mask;
    private IPv4Address broadCast;
    private IPv4Address first;
    private IPv4Address last;

    static {
        try {
            privateNetworks = new Network[]{new Network("10.0.0.0", 8), new Network("172.16.0.0", 12), new Network("192.168.0.0", 16)};
        } catch (InvalidIpAddressException | InvalidMaskLengthException e) {
            e.printStackTrace();
        }
    }

    public Network(String address, int maskLength) throws InvalidIpAddressException, InvalidMaskLengthException {
        IPv4Address temp = new IPv4Address(address);

        validateMask(maskLength);
        this.address = new IPv4Address(temp.getIntAddress() & mask.getIntAddress());
        this.broadCast = new IPv4Address(this.address.getIntAddress() | (~this.mask.getIntAddress()));
        this.first = new IPv4Address(this.address.getIntAddress() + 1);
        this.last = new IPv4Address(this.broadCast.getIntAddress() - 1);
    }

    public String getAddress() {
        return this.address.toString();
    }

    public int getMaskLength() {
        return this.maskLength;
    }

    public String getMask() {
        return this.mask.toString();
    }

    public String getBroadcastAddress() {
        return this.broadCast.toString();
    }

    public String getFirstUsableAddress() {
        return this.first.toString();
    }

    public String getLastUsableAddress() {
        return this.last.toString();
    }

    public boolean contains(String address) throws InvalidIpAddressException {
        IPv4Address ip = new IPv4Address(address);

        return ip.getIntAddress() >= this.address.getIntAddress() && ip.getIntAddress() <= this.broadCast.getIntAddress();
    }

    public int getTotalHosts() {
        return (1 << ( OCTETS_IN_ADDRESS * BITS_IN_BYTE - this.maskLength )) - 2;
    }

    public boolean isPublic() {
        for (Network net : privateNetworks) {
            try {
                if ( net.contains(this.address.toString()) && net.getMaskLength() <= this.maskLength ) {
                    return false;
                }
            } catch (InvalidIpAddressException e) { }
        }
        return true;
    }

    public Network[] getSubnets() throws SplitSubnetException {
        int newMaskLength = this.maskLength + 1;
        IPv4Address newAddress = new IPv4Address(this.address.getIntAddress() + (1 << (OCTETS_IN_ADDRESS * BITS_IN_BYTE - newMaskLength)));
        Network[] subnets = new Network[2];

        try {
            subnets[0] = new Network(this.getAddress(), newMaskLength);
            subnets[1] = new Network(newAddress.toString(), newMaskLength);
        } catch (InvalidIpAddressException | InvalidMaskLengthException e) {
            throw new SplitSubnetException();
        }
        return subnets;
    }

    @Override
    public String toString() {
        return this.getAddress() + '/' + this.maskLength;
    }

    private void validateMask(int maskLength) throws InvalidMaskLengthException {
        int intMask = 0;

        if ( maskLength < 0 && maskLength > OCTETS_IN_ADDRESS * BITS_IN_BYTE ) {
            throw new InvalidMaskLengthException();
        }
        this.maskLength = maskLength;
        intMask = ((1 << this.maskLength) - 1) << (OCTETS_IN_ADDRESS * BITS_IN_BYTE - this.maskLength);
        this.mask = new IPv4Address(intMask);
    }
}
